﻿using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using XamlLocalizer.Tools;

namespace XamlLocalizer.Parsing
{
    public class XamlExtractor
    {
        private const string WpfDefaultNamespace = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";

        public static XamlParseInfo XamlParseInfo { get; private set; }
        private static XamlElementInfo xamlDefElemInfo;

        private static string GetXamlParseInfoFileName(string solutionFolder)
        {
            string infoPath = Path.Combine(solutionFolder, "XamlParseInfo.xml");
            if (File.Exists(infoPath)) return infoPath;

            string homedir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            return Path.Combine(homedir, "XamlParseInfo.xml");
        }

        public static void ReadXamlParseInfo(string solutionFolder)
        {
            var ser = new XmlSerializer(typeof(XamlParseInfo));
            using (var textReader = File.OpenRead(GetXamlParseInfoFileName(solutionFolder)))
            {
                XamlParseInfo = (XamlParseInfo) ser.Deserialize(textReader);
            }
            xamlDefElemInfo = XamlParseInfo.Elements.FirstOrDefault(e => e.Name == "*");
        }

        private XmlDocument doc;

        public XmlDocument Document { get { return doc; } }

        public ParseResult ParseResult { get; private set; }

        public XamlExtractor(string xaml)
        {
            doc = new XmlDocument();
            doc.PreserveWhitespace = true;
            doc.LoadXml(xaml);

            ParseResult = new ParseResult();
            ParseElement(doc.DocumentElement);
        }

        public XamlExtractor(string filename, bool isFileName)
        {
            doc = new XmlDocument();
            doc.PreserveWhitespace = true;
            doc.Load(filename);

            ParseResult = new ParseResult();
            ParseElement(doc.DocumentElement);
        }

        private void ParseElement(XmlElement xmlElement)
        {
            //if (xmlElement.LocalName == LocTextElementName && 
            //    xmlElement.NamespaceURI == XamlParseInfo.ReplaceSettings.LocExtNamespace)
            //{
            //    ParseLocTextElement(xmlElement);
            //    return;
            //}

            XamlElementInfo elmInfo = GetElementInfo(xmlElement);
            ParseAttributes(xmlElement, elmInfo);

            foreach (XmlNode node in xmlElement.ChildNodes)
            {
                if (node.NodeType == XmlNodeType.Element)
                {
                    ParseElement((XmlElement)node);
                }
                else if (node.NodeType == XmlNodeType.Text)
                {
                    ParseTextNode(xmlElement, node);
                }
            }
        }

        //private void ParseLocTextElement(XmlElement xmlElement)
        //{
        //    //TODO:add to list of existing
        //}

        private static XamlElementInfo GetElementInfo(XmlElement xmlElement)
        {
            if (XamlParseInfo.Elements != null)
            {
                return XamlParseInfo.Elements.FirstOrDefault(e => e.Name == xmlElement.LocalName);
            }
            return null;
        }

        private void ParseAttributes(XmlElement xmlElement, XamlElementInfo elmInfo)
        {
            foreach (XmlAttribute att in xmlElement.Attributes)
            {
                ParseAttribute(att, elmInfo);
            }
        }

        private void ParseAttribute(XmlAttribute att, XamlElementInfo elmInfo)
        {
            XamlAttributeInfo attInfo = GetAttributeInfo(att, elmInfo);
            if (attInfo == null)
            {
                attInfo = GetAttributeInfo(att, xamlDefElemInfo);
            }
            if (attInfo != null)
            {
                HandleNode(att, att.Value);
            }
        }

        private static XamlAttributeInfo GetAttributeInfo(XmlAttribute att, XamlElementInfo elmInfo)
        {
            if (elmInfo != null && elmInfo.Attributes != null)
            {
                return elmInfo.Attributes.FirstOrDefault(a => a.Name == att.LocalName);
            }
            return null;
        }

        public string GetWellKnownStringKey(string s)
        {
            if (XamlParseInfo != null && XamlParseInfo.WellKnownStrings != null)
            {
                foreach (var wks in XamlParseInfo.WellKnownStrings)
                {
                    if (String.Compare(s, wks.Text, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        return wks.FullKey;
                    }
                }
            }
            return null;
        }

        private void ParseTextNode(XmlElement xmlElement, XmlNode node)
        {
            string s = node.Value;
            if (String.IsNullOrEmpty(s))
            {
                return;
            }
            s = StripTextNode(s)[1];
            HandleNode(node, s);
        }

        private static string[] StripTextNode(string s)
        {
            string[] res = new string[3];

            int firstSignificantIdx = 0;
            for (; firstSignificantIdx < s.Length; firstSignificantIdx++)
            {
                if (!IsWhiteSpace(s[firstSignificantIdx]))
                {
                    break;
                }
            }

            int lastSignificantIdx = s.Length - 1;
            for (; lastSignificantIdx >= 0; lastSignificantIdx--)
            {
                if (!IsWhiteSpace(s[lastSignificantIdx]))
                {
                    break;
                }
            }

            if (firstSignificantIdx <= lastSignificantIdx)
            {
                if (firstSignificantIdx > 0)
                {
                    res[0] = s.Substring(0, firstSignificantIdx);
                }
                res[1] = s.Substring(firstSignificantIdx, lastSignificantIdx - firstSignificantIdx + 1);
                if (lastSignificantIdx < s.Length - 1)
                {
                    res[2] = s.Substring(lastSignificantIdx + 1, s.Length - lastSignificantIdx - 1);
                }
            }
            return res;
        }

        private static bool IsWhiteSpace(char c)
        {
            return (WhitespaceChars.Contains(c));
        }

        private static readonly char[] WhitespaceChars = new[] { 
            '\t', '\n', '\v', '\f', '\r', ' ', '\x0085', '\x00a0', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 
            ' ', ' ', ' ', ' ', '​', '\u2028', '\u2029', '　', '﻿'
        };


        private static bool IsRootElementAttribute(XmlAttribute attrNode)
        {
            return (attrNode.OwnerElement == attrNode.OwnerDocument.DocumentElement);
        }

        public void ReplaceInXaml(ParseResultNewItem parseItem, ResourceKeyDesc keyDesc, IReplacementStrategy replacementStrategy)
        {
            if (parseItem.Node.NodeType == XmlNodeType.Attribute)
            {
                var attrNode = (XmlAttribute) parseItem.Node;
                if (replacementStrategy.ExtractRootElementAttributesIntoElements && 
                    IsRootElementAttribute(attrNode))
                {
                    ExtractAttributeIntoElement(attrNode, keyDesc, replacementStrategy);
                }
                else
                {
                    parseItem.Node.Value = replacementStrategy.GetAttributeReplacementValue(keyDesc);
                }
            }
            else if (parseItem.Node.NodeType == XmlNodeType.Text)
            {
                string attrToReplaceContent = GetAttrToReplaceContent(parseItem.Node);

                string[] strips = StripTextNode(parseItem.Node.Value);

                var parElem = (XmlElement)parseItem.Node.ParentNode;
                var newElem = replacementStrategy.CreateReplacementElement(parElem.OwnerDocument, keyDesc);
                parElem.ReplaceChild(newElem, parseItem.Node);

                if (!String.IsNullOrEmpty(strips[0]))
                {
                    XmlText txtNode = parElem.OwnerDocument.CreateTextNode(strips[0]);
                    parElem.InsertBefore(txtNode, newElem);
                }

                if (!String.IsNullOrEmpty(strips[2]))
                {
                    XmlText txtNode = parElem.OwnerDocument.CreateTextNode(strips[2]);
                    parElem.InsertAfter(txtNode, newElem);
                }

                if (!String.IsNullOrEmpty(attrToReplaceContent))
                {
                    if (!String.IsNullOrEmpty(parElem.GetAttribute(attrToReplaceContent)))
                    {
                        throw new InvalidOperationException(
                            String.Format("Element has both attribute {0} and content. Full xml='{1}'", 
                            attrToReplaceContent, parElem.OuterXml));
                    }
                    else
                    {
                        //Replacing content with attribute for some elements (controls)
                        parElem.RemoveChild(newElem);
                        parElem.SetAttribute(attrToReplaceContent, replacementStrategy.GetAttributeReplacementValue(keyDesc));
                    }
                }
            }
        }

        private static void ExtractAttributeIntoElement(XmlAttribute attrNode, ResourceKeyDesc keyDesc, IReplacementStrategy replacementStrategy)
        {
            var ownerElement = attrNode.OwnerElement;

            var propertyElement = ownerElement.OwnerDocument.CreateElement(ownerElement.LocalName + "." + attrNode.LocalName, WpfDefaultNamespace);

            ownerElement.RemoveAttributeNode(attrNode);

            var resourcesElement = ownerElement[ownerElement.LocalName + ".Resources", WpfDefaultNamespace];

            if (resourcesElement != null)
            {
                ownerElement.InsertAfter(propertyElement, resourcesElement);
            }
            else
            {
                ownerElement.InsertBefore(propertyElement, ownerElement.FirstChild);
            }

            var valueElement = replacementStrategy.CreateReplacementElement(ownerElement.OwnerDocument, keyDesc);
            propertyElement.AppendChild(valueElement);
        }

        private static string GetAttrToReplaceContent(XmlNode node)
        {
            var elementInfo = GetElementInfo((XmlElement) node.ParentNode);
            return (elementInfo != null) ? elementInfo.ReplaceContentWithAttribute : null;
        }

        private void HandleNode(XmlNode node, string s)
        {
            if (String.IsNullOrEmpty(s) || s.Trim() == "")
            {
                return;
            }
            if (s.StartsWith("{"))
            {
                //if (s.StartsWith(LocTextExtPrefix))
                //{
                //    //TODO: existing
                //    return;
                //}

                //Skip extentions
                return;
            }
            if (!IsSignificant(s))
            {
                return;
            }
            ParseResult.NewItems.Add(new ParseResultNewItem { Node = node, Text = s });
        }

        private static bool IsSignificant(string s)
        {
            for (int i = 0; i < s.Length; i++)
            {
                if (Char.IsLetter(s, i))
                {
                    return true;
                }
            }
            return false;
        }

        public string GetNewXaml()
        {
            var wrt = PrettyXmlWriter.Create();
            doc.WriteTo(wrt);
            return wrt.GetXml();
        }

        public void AddNamespace(string assembly)
        {
            XmlElement rootElem = doc.DocumentElement;
            if (rootElem != null)
            {
                if (XamlParseInfo.ReplaceSettings.AddAssemblyResourcesNamespace)
                {
                    AddNamespace("resources", "clr-namespace:" + assembly);
                }

                if (!String.IsNullOrEmpty(XamlParseInfo.ReplaceSettings.LocExtPrefix))
                {
                    AddNamespace(XamlParseInfo.ReplaceSettings.LocExtPrefix, XamlParseInfo.ReplaceSettings.LocExtNamespace);
                }
            }
        }

        public string AddNamespace(string desiredPrefix, string nmspace)
        {
            XmlElement rootElem = doc.DocumentElement;
            if (rootElem == null) return desiredPrefix;

            string lexNs = rootElem.GetAttribute("xlmns:" + desiredPrefix);
            //TODO: if (lex != nmspace)

            if (String.IsNullOrEmpty(lexNs))
            {
                XmlAttributeCollection docAttribs = rootElem.Attributes;
                XmlNode objNewNode = doc.CreateNode(XmlNodeType.Attribute, "xmlns", desiredPrefix, "http://www.w3.org/2000/xmlns/");
                objNewNode.Value = nmspace;

                docAttribs.Append((XmlAttribute)objNewNode);
            }

            return desiredPrefix;
        }
    }
}
